#include "buff.h"
#include "behaved.h"
#include "map.h"
#include "game.h"
#include "bitem.h"
#include "item.h"

Buff::Buff( uint stackCategory, uint stackCount )
: _behaved( 0 )
, _destroy( false )
, _priority( 0 )
, _sourceItem( 0 )
, _timer( 0 )
, _stackCategory( stackCategory )
, _stackCount( stackCount )
, _isApplyingPostActor( false )
, _isNotCountedInStack( false )
{
}

Buff::~Buff()
{
}

void Buff::SetTarget( Behaved* behaved )
{
    if ( IsDestroyed() )
        throw std::runtime_error( "Setting behaved of a destroyed buff" );

    if ( _behaved )
        _behaved->Remove( this );

    // Breaks adding buffs to things in their constructors; todo OnCreate call for adding buffs after mapset?
    //if ( behaved && !behaved->GetMap() )
    //    throw std::runtime_error( "Attaching buff to unattached behaved" );
    _behaved = behaved ? behaved->GetBase() : behaved;

    if ( !_behaved )
        return;

    ConstBuffList list;
    _behaved->GetAllBuffs( list );
    uint count = 0;
    foreach ( const Buff* b, list ) {
        if ( !b->IsDestroyed() && !b->_isNotCountedInStack && b->GetStackCategory() == GetStackCategory() && GetStackCategory() != 0 )
            ++count;
    }
    if ( count >= GetStackCount() && GetStackCategory() != 0 ) {
        _behaved->Add( this );
        SetSourceItem( 0 );
        Destroy();
    }
    else {
        OnBuffApply();
        _behaved->Add( this );
        behaved->Call< BIOnUpdateBuffs >();
    }
}

const Behaved* Buff::GetTarget() const
{
    return _behaved;
}

Behaved* Buff::GetTarget()
{
    return _behaved;
}

uint Buff::GetStackCategory() const
{
    return _stackCategory;
}

uint Buff::GetStackCount() const
{
    return _stackCount;
}

void Buff::Destroy()
{
    if ( _sourceItem )
        throw std::runtime_error( "Destroying item buff" );

    if ( !_behaved )
        throw std::runtime_error( "Destroying unattached buff" );
    _behaved->GetMap()->GetGame().DestroyBuff( this );
    SetTarget( 0 );
    _destroy = true;
}

bool Buff::IsDestroyed() const
{
    return _destroy;
}

void Buff::StartTimer( uint ticks )
{
    _timer = int( ticks );
}

uint Buff::GetTimer() const
{
    return _timer;
}

bool Buff::IsItemBuff() const
{
    return _sourceItem != 0;
}

void Buff::SetSourceItem( Item* item )
{
    _sourceItem = item;
}

Item* Buff::GetItem()
{
    if ( _sourceItem )
        return _sourceItem;
    throw std::runtime_error( "Getting item of non-item buff" );
}

void Buff::SetPriority( int priority )
{
    _priority = priority;
}

bool Buff::PrioritySort( const Buff* less, const Buff* greater )
{
    if ( less->_priority == greater->_priority )
        return less < greater;
    return less->_priority > greater->_priority;
}

bool Buff::OnTick()
{
    PRE_ACTOR;
    if ( _timer ) {
        --_timer;
        if ( !_timer ) {
            Behaved* b = GetTarget();
            _isNotCountedInStack = true;
            OnBuffExpire();
            if ( !IsDestroyed() )
                Destroy();
            b->Call< BIOnUpdateBuffs >();
        }
    }
    return true;
}

bool Buff::IsApplyingPostActor() const
{
    return _isApplyingPostActor;
}

void Buff::SetApplyingPostActor( bool post ) const
{
    _isApplyingPostActor = post;
}

CompoundBuff::CompoundBuff( uint stackCategory, uint stackCount )
: Buff( stackCategory, stackCount )
, _sourceItem( false )
{
}

void CompoundBuff::SetTarget( Behaved* behaved )
{
    ConstBuffList list;
    if ( _behaved )
        _behaved->GetAllBuffs( list );
    uint count = 0;
    foreach ( const Buff* b, list ) {
        if ( b->GetStackCategory() == GetStackCategory() && GetStackCategory() != 0 )
            ++count;
    }

    foreach ( Buff* b, _buffs ) {
        b->SetTarget( behaved );
        if ( !b->IsDestroyed() && count > GetStackCount() )
            b->Destroy();
    }
    if ( count > GetStackCount() )
        _buffs.clear();
    Buff::SetTarget( behaved );
}

void CompoundBuff::Destroy()
{
    foreach ( Buff* b, _buffs )
        b->Destroy();
    _buffs.clear();
    Buff::Destroy();
}

void CompoundBuff::AddBuff( Buff* buff )
{
    _buffs.push_back( buff );
    buff->SetTarget( GetTarget() );
    if ( _sourceItem )
        buff->SetSourceItem( GetItem() );
}

void CompoundBuff::SetSourceItem( Item* item )
{
    _sourceItem = item != 0;
    Buff::SetSourceItem( item );
    foreach ( Buff* b, _buffs )
        b->SetSourceItem( item );
}